﻿using System;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using CI.Interfaces;

namespace CI.Organism
{
    /// <summary>Represents a neuron, base class</summary>
    [DataContract(IsReference = true)]
    public abstract class Neuron : Cell, INeuron
    {

        #region Properties

        /// <summary> 
        /// List of the input Synapes
        /// </summary>
        [DataMember]
        public ISynapseCollection InputSynapse { get; set; }

        /// <summary>
        /// List of the output Synapes
        /// </summary>
        [DataMember]
        public ISynapseCollection OutputSynapse { get; set; }

        /// <summary>
        /// Threshold value where activation takes place on the resulting output value of the activation function
        /// </summary>
        [DataMember]
        public long ThresholdValue { get; set; }

        /// <summary>
        /// Last value evalated against the threshold by the Activation Function
        /// </summary>
        public long SummedInputValue { get; set; }

        /// <summary>
        /// Neural system the neuron belongs to
        /// </summary>
        [DataMember]
        public INeuralSystem NeuralSystem { get; set; }

        /// <summary>
        /// Notify any attachments that the activation has fired with a value
        /// </summary>
        public event Action<long> ActivationEvent;

        /// <summary>
        /// Notify that we have finised calculating the activation function
        /// </summary>
        public event Action ActivationFunctionComplete;

        #endregion

        protected static SimEnv _simEnv { get { return SimEnv.Instance; } }

        /// <summary>
        /// Create a neuron and save its threshold value
        /// On creation register the neuron in the Neural system
        /// </summary>
        /// <param name="thresholdValue">Threshold value</param>
        /// <param name="neuralSystem">Neural system the neuron belongs to</param>
        /// <param name="guid">Neurons GUID</param>
        public Neuron(Int64 thresholdValue, INeuralSystem neuralSystem, Guid guid)
            : base(neuralSystem.Organism, guid)
        {
            ThresholdValue = 0;

            #region Argument check
            // note the base class will blow up before we get here.
            if (neuralSystem == null)
                throw new ArgumentException("NeuralSystem");
            if (guid == Guid.Empty)
                throw new ArgumentException("guid");
            #endregion

            // save the threshold for activation value
            ThresholdValue = thresholdValue;
            // save the neural system we belong to
            NeuralSystem = neuralSystem;

            // create the input and output synpatic colleciton and set them to this neuron
            InputSynapse = new SynapseCollection();
            OutputSynapse = new SynapseCollection();
        }

        /// <summary>
        /// A neuron with a reduced constructor to be compatible with mutation
        /// </summary>
        /// <param name="organism">Organism</param>
        /// <param name="guid">guid</param>
        public Neuron(IOrganism organism, Guid guid) : this(0, organism.NeuralSystem, guid) { }

        /// <summary>
        /// Activation function that should be overriden
        /// </summary>
        /// <returns>result</returns>
        public abstract bool ActivationFunction(UInt32 neuralSystemHB);

        /// <summary>
        /// Mutate the Neuron
        /// 1 - Threshold value
        /// 2 - Type of Neuron? possibly???
        /// </summary>
        /// <param name="mutationRate">mutation rate</param>
        /// <param name="childOrganism">new child Organism it will belong to</param>
        /// <returns>new Neuron cell</returns>
        public override ICell Mutate(double mutationRate, IOrganism childOrganism)
        {
            // code taken from
            // http://www.muellerdesigns.net/dasblog/2005/07/18/AnExampleOfReflectionUsingC.aspx
            // or perhaps refactor this (INeuralSystem)this.Organism.NeuralSystem.GetType().GetConstructor(new Type[] { this.Organism.GetType() }).Invoke(new object[] { _Organism })

            var classType = GetType();

            // We are anticipating a constructor that takes 3 parameters,
            var conTypes = new Type[3];
            conTypes[0] = typeof(Int64);
            conTypes[1] = typeof(INeuralSystem);
            conTypes[2] = typeof(Guid);

            // Get the constructor that adheres to our parameters.
            ConstructorInfo constructor = classType.GetConstructor(conTypes);

            // Define the parameters to pass into our constructor when we call it.
            var constructorParams = new object[3];
            // mutate the threshold
            constructorParams[0] = Mutation.MutateCrawlOrLeap(ThresholdValue, mutationRate);
            constructorParams[1] = childOrganism.NeuralSystem;
            constructorParams[2] = Guid;
            _simEnv.TraceLogger.LogDebug(string.Format("Invoking constructor of type {0}", classType));

            // Invoke the constructor dynamically, getting an instance of the class.            
            var childcell = constructor.Invoke(constructorParams) as INeuron;

            childcell.InputSynapse = new SynapseCollection();
            childcell.OutputSynapse = new SynapseCollection();

            foreach (var inputSynapse in InputSynapse)
            {
                childcell.InputSynapse.Add(inputSynapse.Mutate(mutationRate, childOrganism));
            }

            foreach (var outputSynapse in OutputSynapse)
            {
                childcell.OutputSynapse.Add(outputSynapse.Mutate(mutationRate, childOrganism));
            }

            return childcell;
        }

        /// <summary>
        /// Add the cell to the neural system, this being a neuron we have to bind the synapes
        /// </summary>
        public override void AddCell()
        {
            // call the base function
            base.AddCell();

            // adding the neuron in the neural system
            _simEnv.TraceLogger.LogDebug(string.Format("Adding Neuron {0} in neural system", this.Guid));
            NeuralSystem.AddNeuron(this);
        }

        /// <summary>
        /// Delete the cell from the neural system
        /// </summary>
        public override void DelCell()
        {
            // call the base function
            base.DelCell();

            // deleting the neuron in the neural system
            _simEnv.TraceLogger.LogDebug(string.Format("Deleting Neuron {0} from neural system", this.Guid));
            NeuralSystem.DelNeuron(this);
        }

        /// <summary>
        /// Fire the activation event notifier
        /// </summary>
        /// <param name="summedInputValue"></param>
        public void ActivationEventNotifier(long summedInputValue)
        {
            if (ActivationEvent != null)
            {
                _simEnv.TraceLogger.LogDebug("Firing event notifier on neuron :" + Guid);
                ActivationEvent(summedInputValue);
            }
        }

        public void RaiseActivationFunctionComplete()
        {
            if (ActivationFunctionComplete != null)
                ActivationFunctionComplete();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            ActivationEvent = null;
            ActivationFunctionComplete = null;
        }

        /// <summary>
        /// Fire the synapse, notify it that a potential has arrived and the value
        /// </summary>
        public abstract void FireInput(long inputValue);


        /// <summary>
        /// To String override
        /// </summary>
        /// <returns></returns>
        public override string ToString(string value)
        {
            // create the string
            var sb = new StringBuilder();

            // add the threshold
            sb.Append("Threshold Value : ");
            sb.Append(ThresholdValue);

            // add all the input synapes
            sb.Append(Environment.NewLine);
            sb.Append("Input Synapes : ");
            sb.Append(InputSynapse.Count);
            foreach (var synapse in InputSynapse)
            {
                sb.Append(Environment.NewLine);
                sb.Append(synapse.ToString());
            }

            // add all the output synapses
            sb.Append(Environment.NewLine);
            sb.Append("Output Synapes : ");
            sb.Append(OutputSynapse.Count);
            foreach (var synapse in OutputSynapse)
            {
                sb.Append(Environment.NewLine);
                sb.Append(synapse.ToString());
            }

            // return the result
            return sb.ToString();
        }

        /// <summary>
        /// do a equals comparison of one cell type to another only comparing the value types, no refference comparisons
        /// will call the check for synapses
        /// </summary>
        /// <param name="cell">cell to compare against</param>
        /// <returns>true if equal value types</returns>
        public override bool EqualsValueTypes(ICell cell)
        {
            // check the main value types(threshold)
            if (ThresholdValue != ((INeuron)cell).ThresholdValue)
                return false;

            // check the synapse counts
            if (InputSynapse.Count != ((INeuron)cell).InputSynapse.Count)
                return false;
            if (OutputSynapse.Count != ((INeuron)cell).OutputSynapse.Count)
                return false;

            // check all the synapses
            foreach (ISynapse synapse in OutputSynapse)
            {
                // find the synapse
                ISynapse foundSynapse = ((INeuron)cell).OutputSynapse.Find(synapse.Guid);

                // check and make sure one was found
                if (foundSynapse == null)
                    return false;

                // compare it
                if (!synapse.EqualsValueTypes(foundSynapse))
                    return false;
            }

            // all good
            return true;
        }

    }
}